﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CrankShaftAnalysisSystem.Controller;
using CrankShaftAnalysisSystem.Devices;
using System.Text.Json;
using System.IO;
using CrankShaftResultHandler;

namespace CrankShaftAnalysisSystem
{
    public class MeasureSystem
    {
        private static readonly MeasureSystem instance = new MeasureSystem();

        /// <summary>
        /// 显式的静态构造函数用来告诉C#编译器在其内容实例化之前不要标记其类型
        /// 静态构造函数是C#的一个新特性，其实好像很少用到。 
        /// 不过当我们想初始化一些静态变量的时候就需要用到它了。 这个构造函数是属于类的，
        /// 而不是属于哪里实例的，就是说这个构造函数只会被执行一次。 
        /// 也就是在创建第一个实例或引用任何静态成员之前，由.NET自动调用
        /// </summary>
        static MeasureSystem() { }

        [Description("东方量仪设备")]
        public DistanceEastenGauge Easten { get; set; }

        [Description("海德汉直线光栅尺")]
        public LinearLC485 LC485 { get; set; }

        [Description("海德汉笔式传感器")]
        public DistanceMT2571 MT2571 { get; set; }

        [Description("海德汉角度编码器")]
        public EncoderERM2400 ERM2400 { get; set; }

        [Description("PLC设备")]
        public PLCMaster PLC { get; set; }

        [Description("测量进度")]
        public double Process { get; set; }

        [Description("测量启动时间")]
        public DateTime StartTime { get; private set; }

        [Description("测量耗时")]
        public TimeSpan MeasureDuration { get; private set; }

        [Description("当前绑定的数据")]
        public DataStorage DataBase { get; private set; }

        [Description("测量时的相对角度")]
        public double RelativeDegrees { get; private set; }

        private MeasureSystem()
        {
            Easten = DistanceEastenGauge.Instance;
            LC485 = LinearLC485.Instance;
            MT2571 = DistanceMT2571.Instance;
            ERM2400 = EncoderERM2400.Instance;
            PLC = PLCMaster.Instance;
            StartTime = DateTime.Now;
        }        

        public enum State
        {
            IDLE,
            BUSY,
            FINISHED
        }

        public State OneStepMeasureState { get; private set; } = State.IDLE;
        public State MeasureState { get; private set; } = State.IDLE;
        public bool IsManualStop { get; private set; } = false;
        public static PointMeasureConfig pointMeasureConfig { get; private set; }
        public static PointMeasureConfig.Configs MeasureSetting { get; set; }

        [Description("单个高度的测量总角度")]
        public double MeasuringDegrees { get; set; } = 360.0;

        [Description("测量的时候的转速")]
        public int MeasuringRPM { get; set; } = 600;
        public const int PreMeasuringRPM = 600; // 跑合速度
        public int TotalMeasurePoints { get; private set; }
        public int CurrentMeasurePointIndex { get; private set; } = 0;
        public string CurrentTaskName { get; private set; } = "等待测量开始";

        public void SafetyFinishMeaure()
        {
            MeasureState = State.FINISHED;
        }
        public void StopMeasuring()
        {
            IsManualStop = true;
        }

        public void LoadPointConfig(string path)
        {
            using (var file = new StreamReader(path))
            {
                var cfg = file.ReadToEnd();
                pointMeasureConfig = JsonSerializer.Deserialize<PointMeasureConfig>(cfg);
                MeasureSetting = typeof(PointMeasureConfig).GetProperty(pointMeasureConfig.CurrentType).GetValue(pointMeasureConfig) as PointMeasureConfig.Configs;
                MeasureSetting.Tolerance.CopyTo(ref ResultHandler.Tolerance);
            }
        }

        public void Begin()
        {
            if (Properties.Settings.Default.OfflineMode)
            {
                Console.WriteLine("System Run As Offline Mode");
                return;
            }

            Easten.LoadSetting();
            Easten.Device.begin();
            LC485.Begin();
            MT2571.Begin();
            ERM2400.Device.Begin();
            PLC.Device.begin();
        }

        /// <summary>
        /// 旋转使得转台刚刚越过零点
        /// </summary>
        /// <returns></returns>
        private Task MoveThroughZero()
        {
            return Task.Run(() =>
            {
                PLC.SetSpeed(1, -100);
                while (true)
                {
                    var rad = ERM2400.Device.ReadAngleRadians(out _);
                    if (rad > 0)
                    {
                        Console.WriteLine("Move Through Zero Finish");
                        break;
                    }
                    if (SafeController.ManualStop)
                    {
                        Console.WriteLine("[Safe Controller]: StopAll.");
                        break;
                    }
                    Thread.Sleep(100);
                }
                PLC.SetSpeed(1, 0);
            });
        }

        public Task VacuumOn()
        {
            return Task.Run(() =>
            {
                Console.WriteLine("Vacuum On.");
                LogicalController.VacuumOn();
            });
        }

        public Task VacuumOff()
        {
            return Task.Run(() =>
            {
                Console.WriteLine("Vacuum Off.");
                LogicalController.VacuumOff();
            });
        }

        public Task DrawBackMT2571()
        {
            return Task.Run(() =>
            {
                OneStepMeasureState = State.BUSY;
                Console.WriteLine("Start Drawing Back MT2571.");
                if (SafeController.ManualStop)
                {
                    Console.WriteLine("[Safe Controller]: StopAll.");
                    return;
                }
                LogicalController.MT2571Back();
                while (true)
                {
                    if (PLC.MT2571IsBack)
                        break;
                    Thread.Sleep(100);
                }
                Console.WriteLine("MT2571 Is Back.");
                OneStepMeasureState = State.IDLE;
            });
        }

        public Task WaitMT2571Ref()
        {
            return Task.Run(() =>
            {
                CurrentTaskName = "请轻轻推动MT2571直至找到参考点";
                while(true)
                {
                    if(MT2571.Device.RefIndexFound)
                    {
                        var init_dis = MT2571.GetDistance();
                        if(Math.Abs(init_dis - MT2571.InitDistance) < 200 )
                            break;
                    }
                    Thread.Sleep(100);
                }
            });
        }

        public Task StretchOutMT2571()
        {
            return Task.Run(() =>
            {
                double mt2571_last = 0.0;
                double mt2571_current = 0.0;
                double mt2571_err = 1000.0;
                OneStepMeasureState = State.BUSY;
                Console.WriteLine("Start Stretching MT2571.");
                if (SafeController.ManualStop)
                {
                    Console.WriteLine("[Safe Controller]: StopAll.");
                    return;
                }
                LogicalController.MT2571Out();
                while (true)
                {
                    if (PLC.MT2571IsOut)
                    {
                        //额外等一段时间，等MT2571数据稳定
                        Thread.Sleep(500);
                        mt2571_last = mt2571_current;
                        mt2571_current = MT2571.GetDistance();
                        mt2571_err = mt2571_current - mt2571_last;
                        if (Math.Abs(mt2571_err)<0.3)
                            break;
                    }
                    Thread.Sleep(100);
                }
                Console.WriteLine($"MT2571 Is Out, Last: {mt2571_last}, Now: {mt2571_current}");
                OneStepMeasureState = State.IDLE;
            });
        }

        /// <summary>
        /// 在指定高度测一组点，阻塞
        /// </summary>
        /// <param name="height"></param>
        private Task MeasureAtHeight(int height, int mark_index)
        {
            return Task.Run(() =>
            {
                CurrentMeasurePointIndex = mark_index;
                CurrentTaskName = $"第 [{mark_index + 1}]/{TotalMeasurePoints} 个点测量中...";
                Console.WriteLine(CurrentTaskName);
                VacuumOn().
                ContinueWith(task => DrawBackMT2571()).Result.
                ContinueWith(task => LinearController.SetPosition(height)).Result.
                ContinueWith(task => VacuumOff()).Result.
                ContinueWith(task => StretchOutMT2571()).Result.
                ContinueWith(task => DoOnePointMeasure(DataBase, mark_index)).Wait();
                while(true)
                {
                    Thread.Sleep(100);
                    if(OneStepMeasureState == State.FINISHED)
                    {
                        break;
                    }
                    if (SafeController.ManualStop)
                    {
                        Console.WriteLine("[Safe Controller]: StopAll.");
                        break;
                    }
                }
            });
        }

        public void MeasureTotalPoints(DataStorage data_base)
        {
            bool finished = false;
            if(MeasureState == State.BUSY)
            {
                Console.WriteLine("[Measure Error]: Busy Measuring.");
                return;
            }
            CurrentTaskName = "测量开始";
            MeasureState = State.BUSY;
            // 准备阶段刷新启动时间
            StartTime = DateTime.Now;
            Task.Run(() =>
            {
                CurrentMeasurePointIndex = 0;
                int[] points = new int[] {
                    MeasureSetting.P0,
                    MeasureSetting.P1,
                    MeasureSetting.P2,
                    MeasureSetting.P3,
                    MeasureSetting.P4,
                    MeasureSetting.P5,
                    MeasureSetting.P6,
                    MeasureSetting.P7,
                };
                TotalMeasurePoints = points.Length;
                DataBase = data_base;
                WaitMT2571Ref().Wait();
                WaitAutoLedOn().Wait();
                CurrentTaskName = "顶尖下落中";
                PressureController.PressPart().Wait();
                DrawBackMT2571().Wait();
                PrepareMeasure().Wait();
                for (int i = 0; i < points.Length; i++)
                    MeasureAtHeight(points[i], i).Wait();
                CurrentTaskName = "等待回零";
                var end_task = DrawBackMT2571().
                ContinueWith(task => AngleController.SetPosition(0)).
                ContinueWith(task => LinearController.SetPosition(points[0])).
                ContinueWith(task => PressureController.MoveUp(25));
                CurrentTaskName = "等待机械归位";
                while (true)
                {
                    if (LogicalController.EncoderNearZero)
                    {
                        finished = true;
                    }
                    if (
                        finished && 
                        LinearController.State != State.BUSY && 
                        AngleController.State != State.BUSY && 
                        PressureController.State != State.BUSY
                    ){
                        Console.WriteLine("All Finished");
                        CurrentTaskName = "测量完成";
                        MeasureState = State.FINISHED;
                        break;
                    }
                    Thread.Sleep(100);
                }
            });

        }

        private Task WaitAutoLedOn()
        {
            return Task.Run(() =>
            {
                if (OneStepMeasureState == State.BUSY)
                {
                    Console.WriteLine("[Measure ERROR]: Busy Measuring");
                    return;
                }
                CurrentTaskName = "请长按自动启动2秒，开始测量";
                Console.WriteLine("Waiting Auto Led On");
                OneStepMeasureState = State.BUSY;
                while(true)
                {
                    if (IsManualStop)
                    {
                        IsManualStop = false;
                        Console.WriteLine("Stopped Manually.");
                        break;
                    }
                    if (SafeController.ManualStop)
                    {
                        Console.WriteLine("[Safe Controller]: StopAll.");
                        break;
                    }
                    if (PLC.AutoRunLedOn)
                        break;
                    Thread.Sleep(100);
                }
                EndOfOneTask(State.IDLE);
            });
        }

        private Task PrepareMeasure()
        {
            return Task.Run(() =>
            {
                // 启动跑合
                if (OneStepMeasureState == State.BUSY)
                {
                    Console.WriteLine("[Measure ERROR]: Busy Measuring");
                    return;
                }
                CurrentTaskName = "跑合中...";
                Console.WriteLine("Start Preparing, First Run Empty Loop.");
                Process = 0;
                OneStepMeasureState = State.BUSY;
                ERM2400.Device.ClearMultiTurn(); // 清除之前积累的多圈
                double StartDegree = ERM2400.Device.TotalDegrees;
                PLC.SetSpeed(1, -PreMeasuringRPM);
                while (true)
                {
                    MeasureDuration = DateTime.Now - StartTime;
                    double deg = 0;
                    ERM2400.Device.ReadAngleRadians(out deg);
                    RelativeDegrees = deg - StartDegree;

                    if (RelativeDegrees >= 360)
                    {
                        Console.WriteLine($"RelativeDegrees: {RelativeDegrees}, Stop Measure.");
                        break;
                    }
                    if (SafeController.ManualStop)
                    {
                        Console.WriteLine("[Safe Controller]: StopAll.");
                        break;
                    }
                    if (IsManualStop)
                    {
                        IsManualStop = false;
                        Console.WriteLine("Stopped Manually.");
                        break;
                    }
                    Process = RelativeDegrees / MeasuringDegrees * 100;
                    if (Process > 100) Process = 99.9;
                    Thread.Sleep(100);
                }
                EndOfOneTask(State.IDLE);
            });
        }

        /// <summary>
        /// 测量单个点，这个仅仅是测量操作，不要单独用这个方法
        /// </summary>
        /// <param name="data_base">记录点的数据集</param>
        /// <param name="mark_index">点的序号</param>
        /// <returns></returns>
        private Task DoOnePointMeasure(DataStorage data_base, int mark_index)
        {
            return Task.Run(() =>
            {
                Console.WriteLine($"Start Do Measure Task: {mark_index}");
                if (OneStepMeasureState == State.BUSY)
                {
                    Console.WriteLine("[Measure ERROR]: Busy Measuring");
                    return;
                }
                Process = 0;
                OneStepMeasureState = State.BUSY;
                DataBase = data_base;
                ERM2400.Device.ClearMultiTurn(); // 清除之前积累的多圈
                double StartDegree = ERM2400.Device.TotalDegrees;
                PLC.SetSpeed(1, -MeasuringRPM);
                while (true)
                {
                    var p = new DataStorage.CurvePoint();
                    double deg = 0;
                    p.Stamp = DateTime.Now;
                    p.p = MT2571.GetDistance();
                    p.Phi = ERM2400.Device.ReadAngleRadians(out deg);
                    RelativeDegrees = deg - StartDegree;
                    p.Z = LC485.ReadLinearDis();
                    p.Pen1 = Easten.Device.ChannelData[2];
                    p.Pen2 = Easten.Device.ChannelData[3];
                    p.Pen3 = Easten.Device.ChannelData[4];
                    p.PointMark = $"P{mark_index}";
                    data_base.Clouds.Add(p);
                    MeasureDuration = DateTime.Now - StartTime;
                    if (RelativeDegrees >= MeasuringDegrees)
                    {
                        Console.WriteLine($"RelativeDegrees: {RelativeDegrees}, Stop Measure.");
                        break;
                    }
                    if (IsManualStop)
                    {
                        IsManualStop = false;
                        Console.WriteLine("Stopped Manually.");
                        break;
                    }
                    if (SafeController.ManualStop)
                    {
                        Console.WriteLine("[Safe Controller]: StopAll.");
                        break;
                    }
                    Process = RelativeDegrees / MeasuringDegrees * 100;
                    if (Process > 100) Process = 99.9;
                    Thread.Sleep(10);
                }
                PLC.SetSpeed(1, 0);
                Process = 100.0;
                MeasureDuration = DateTime.Now - StartTime;
                OneStepMeasureState = State.FINISHED;
            });
        }

        private void EndOfOneTask(State stop_state)
        {
            PLC.SetSpeed(1, 0);
            Process = 100.0;
            MeasureDuration = DateTime.Now - StartTime;
            OneStepMeasureState = stop_state;
        }

        public bool CalibrateSystem(string file)
        {
            var handler = new CrankShaftResultHandler.ResultHandler();
            handler.LoadRefrenceAndConfig(MeasureSetting.RefrenceReport);
            var h = handler.ReadCSV(file)?.Deal().ReCalibration().SaveCAL();
            if (h == null)
                return false;
            for (int i = 0; i < 4; i++)
                handler.CAL.DiameterRatio[i] = 1.0;
            //再计算一遍确定直径校准
            handler.ReadCSV(file)?.Deal();
            handler.CAL.DiameterRatio[0] = handler.Refrence.d4 / handler.Diameter0;
            handler.CAL.DiameterRatio[1] = handler.Refrence.d6 / handler.Diameter1;
            handler.CAL.DiameterRatio[2] = handler.Refrence.d7 / handler.Diameter2;
            handler.CAL.DiameterRatio[3] = handler.Refrence.d5 / handler.Diameter3;
            handler.SaveCAL();
            handler.WriteCalibrationHeader(); // 校准成功在历史文件里面记录
            return true;
        }

        public static MeasureSystem Instance
        {
            get
            {
                return instance;
            }
        }
    }
}
